/*
 * ArcEmu MMORPG Server
 * Copyright (C) 2008-2010 <http://www.ArcEmu.org/>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "LogonStdAfx.h"
#include <signal.h>
#ifndef WIN32
#include <sys/resource.h>
#endif
#include "../server_share/arcemu_getopt.h"

#define BANNER "ArcEmu %s r%u/%s-%s (%s) :: Logon Server"

#ifndef WIN32
#include <sched.h>
#endif

// Database impl
Database * sLogonSQL;
initialiseSingleton(LogonServer);
bool mrunning = true;
Mutex _authSocketLock;
set<AuthSocket*> _authSockets;

/*** Signal Handler ***/
void _OnSignal(int s)
{
	switch (s)
	{
#ifndef WIN32
	case SIGHUP:
	   {
		   sLog.outString("Received SIGHUP signal, reloading accounts.");
		   AccountMgr::getSingleton().ReloadAccounts(true);
	   }break;
#endif
	case SIGINT:
	case SIGTERM:
	case SIGABRT:
#ifdef _WIN32
	case SIGBREAK:
#endif
		mrunning = false;
		break;
	}

	signal(s, _OnSignal);
}

int main(int argc, char** argv)
{
#ifndef WIN32
	rlimit rl;
	if (getrlimit(RLIMIT_CORE, &rl) == -1)
		printf("getrlimit failed. This could be problem.\n");
	else
	{
		rl.rlim_cur = rl.rlim_max;
		if (setrlimit(RLIMIT_CORE, &rl) == -1)
			printf("setrlimit failed. Server may not save core.dump files.\n");
	}
#endif

	new LogonServer;

	// Run!
	LogonServer::getSingleton( ).Run(argc, argv);
	delete LogonServer::getSingletonPtr();
}


/**
  * Initialises the logon database
  *
  * Reads the configs\logon.conf file and parses the <LogonDatabase> tag
  *
  * Any errors in this file, such as a missing parameter should be caught 
  * and the user notified in an intelligent way
  *
  * If no errors are found, the database is initialized
  **/
bool startdb()
{
	string lhostname, lusername, lpassword, ldatabase;
	int lport = 0;
	int ltype = 1;
	// Configure Main Database

	bool result;

	// Set up reusable parameter checks for each parameter
	// Note that the Config.MainConfig.Get[$type] methods returns boolean value and not $type

	bool existsUsername = Config.MainConfig.GetString("LogonDatabase", "Username", &lusername);
	bool existsPassword = Config.MainConfig.GetString("LogonDatabase", "Password", &lpassword);
	bool existsHostname = Config.MainConfig.GetString("LogonDatabase", "Hostname", &lhostname);
	bool existsName     = Config.MainConfig.GetString("LogonDatabase", "Name",     &ldatabase);
	bool existsPort     = Config.MainConfig.GetInt(   "LogonDatabase", "Port",     &lport    );
	bool existsType     = Config.MainConfig.GetInt(   "LogonDatabase", "Type",     &ltype    );

	// Configure Logon Database...

	// logical AND every parameter to ensure we catch any error
	result = existsUsername && existsPassword && existsHostname && existsName && existsPort && existsType;

	if( !result )
	{
		//Build informative error message
		//Built as one string and then printed rather than calling sLog.outString(...) for every line,
		//  as experiments has seen other thread write to the console inbetween calls to sLog.outString(...)
		//  resulting in unreadable error messages.
		//If the <LogonDatabase> tag is malformed, all parameters will fail, and a different error message is given

		string errorMessage = "sql: Certain <LogonDatabase> parameters not found in configs\\logon.conf \r\n";
		if( !(existsHostname || existsUsername || existsPassword  ||  
			  existsName     || existsPort     || existsType) )
		{
			errorMessage += "  Double check that you have remembered the entire <LogonDatabase> tag.\r\n";
			errorMessage += "  All parameters missing. It is possible you forgot the first '<' character.\r\n";
		}
		else
		{
			errorMessage +=                        "  Missing paramer(s):\r\n";
			if( !existsHostname ){ errorMessage += "    Hostname\r\n" ; }
			if( !existsUsername ){ errorMessage += "    Username\r\n" ; }
			if( !existsPassword ){ errorMessage += "    Password\r\n" ; }
			if( !existsName     ){ errorMessage += "    Name\r\n"; }
			if( !existsPort     ){ errorMessage += "    Port\r\n"; }
			if( !existsType     ){ errorMessage += "    Type\r\n"; }
		}

		sLog.outString( errorMessage.c_str());
		return false;
	}

	sLog.SetScreenLoggingLevel(Config.MainConfig.GetIntDefault("LogLevel", "Screen", 0));
	sLogonSQL = Database::CreateDatabaseInterface( ltype );

	// Initialize it
	if(!sLogonSQL->Initialize(lhostname.c_str(), (unsigned int)lport, lusername.c_str(),
		lpassword.c_str(), ldatabase.c_str(), Config.MainConfig.GetIntDefault("LogonDatabase", "ConnectionCount", 5),
		16384))
	{
		sLog.outError("sql: Logon database initialization failed. Exiting.");
		return false;
	}

	return true;
}

#define DEF_VALUE_NOT_SET 0xDEADBEEF


Mutex m_allowedIpLock;
vector<AllowedIP> m_allowedIps;
vector<AllowedIP> m_allowedModIps;

bool IsServerAllowed(unsigned int IP)
{
	return true;
}

bool IsServerAllowedMod(unsigned int IP)
{
	m_allowedIpLock.Acquire();
	for(vector<AllowedIP>::iterator itr = m_allowedModIps.begin(); itr != m_allowedModIps.end(); ++itr)
	{
		if( ParseCIDRBan(IP, itr->IP, itr->Bytes) )
		{
			m_allowedIpLock.Release();
			return true;
		}
	}
	m_allowedIpLock.Release();
	return false;
}

bool Rehash()
{
#ifdef WIN32
	char * config_file = "configs/logon.conf";
#else
	char * config_file = (char*)CONFDIR "/logon.conf";
#endif
	if(!Config.MainConfig.SetSource(config_file))
	{
		printf("Config file could not be rehashed.\n");
		return false;
	}

	// re-set the allowed server IP's
	string ips = Config.MainConfig.GetStringDefault("LogonServer", "AllowedIPs", "");
	string ipsmod = Config.MainConfig.GetStringDefault("LogonServer", "AllowedModIPs", "");

	vector<string> vips = StrSplit(ips, " ");
	vector<string> vipsmod = StrSplit(ips, " ");

	m_allowedIpLock.Acquire();
	m_allowedIps.clear();
	m_allowedModIps.clear();
	vector<string>::iterator itr;
	for(itr = vips.begin(); itr != vips.end(); ++itr)
	{
		string::size_type i = itr->find("/");
		if( i == string::npos )
		{
			printf("IP: %s could not be parsed. Ignoring\n", itr->c_str());
			continue;
		}

		string stmp = itr->substr(0, i);
		string smask = itr->substr(i+1);

		unsigned int ipraw = MakeIP(stmp.c_str());
		unsigned char ipmask = (char)atoi(smask.c_str());
		if( ipraw == 0 || ipmask == 0 )
		{
			printf("IP: %s could not be parsed. Ignoring\n", itr->c_str());
			continue;
		}

		AllowedIP tmp;
		tmp.Bytes = ipmask;
		tmp.IP = ipraw;
		m_allowedIps.push_back(tmp);
	}

	for(itr = vipsmod.begin(); itr != vipsmod.end(); ++itr)
	{
		string::size_type i = itr->find("/");
		if( i == string::npos )
		{
			printf("IP: %s could not be parsed. Ignoring\n", itr->c_str());
			continue;
		}

		string stmp = itr->substr(0, i);
		string smask = itr->substr(i+1);

		unsigned int ipraw = MakeIP(stmp.c_str());
		unsigned char ipmask = (char)atoi(smask.c_str());
		if( ipraw == 0 || ipmask == 0 )
		{
			printf("IP: %s could not be parsed. Ignoring\n", itr->c_str());
			continue;
		}

		AllowedIP tmp;
		tmp.Bytes = ipmask;
		tmp.IP = ipraw;
		m_allowedModIps.push_back(tmp);
	}

	m_allowedIpLock.Release();

	return true;
}


void LogonServer::Run(int argc, char ** argv)
{
	UNIXTIME = time(NULL);
	g_localTime = *localtime(&UNIXTIME);
#ifdef WIN32
	char * config_file = "configs/logon.conf";
#else
	char * config_file = (char*)CONFDIR "/logon.conf";
#endif
	int file_log_level = DEF_VALUE_NOT_SET;
	int screen_log_level = DEF_VALUE_NOT_SET;
	int do_check_conf = 0;
	int do_version = 0;

	struct arcemu_option longopts[] =
	{
		{ "checkconf",			arcemu_no_argument,				&do_check_conf,			1		},
		{ "screenloglevel",		arcemu_required_argument,		&screen_log_level,		1		},
		{ "fileloglevel",		arcemu_required_argument,		&file_log_level,		1		},
		{ "version",			arcemu_no_argument,				&do_version,			1		},
		{ "conf",				arcemu_required_argument,		NULL,					'c'		},
		{ 0, 0, 0, 0 }
	};

	int c;
	while ((c = arcemu_getopt_long_only(argc, argv, ":f:", longopts, NULL)) != -1)
	{
		switch (c)
		{
		case 'c':
			/* Log filename was set */
			config_file = new char[strlen(arcemu_optarg)];
			strcpy(config_file,arcemu_optarg);
			break;
		case 0:
			break;
		default:
			sLog.m_fileLogLevel = -1;
			sLog.m_screenLogLevel = 3;
			printf("Usage: %s [--checkconf] [--screenloglevel <level>] [--fileloglevel <level>] [--conf <filename>] [--version]\n", argv[0]);
			return;
		}
	}

	// Startup banner
	if(!do_version && !do_check_conf)
	{
		sLog.Init(-1, 3);
	}
	else
	{
		sLog.m_fileLogLevel = -1;
		sLog.m_screenLogLevel = 3;
	}
	
	sLog.outString(BANNER, "TEST", 1, CONFIG, PLATFORM_TEXT, ARCH);
	Log.Color(TBLUE);
	printf("\nCopyright (C) 2008-2010 ArcEmu. http://www.arcemu.org/\n");
	printf("This program is free software: you can redistribute it and/or modify\n");
	printf("it under the terms of the GNU Affero General Public License as published by\n");
	printf("the Free Software Foundation, either version 3 of the License, or\n");
	printf("any later version.\n");
	printf("This program is distributed in the hope that it will be useful,\n");
	printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
	printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
	printf("GNU Affero General Public License for more details.\n");
	printf("                                                \n");
	printf("                     ``````                     \n");
	printf("    ArcEmu!        `/o/::-:/-                   \n"); 
	printf("                   oho/-.-:yN-                  \n"); 
	printf("                    os+/-.:::                   \n"); 
	printf("                    :ysyoo+:`                   \n"); 
	printf("					`ohdys/.                    \n"); 
	printf("                     oyho/-`   ``               \n"); 
	printf("                   `shyo+:./ssmdsyo:`           \n"); 
	printf("                    .shss+:yNMMNNMNmms.         \n"); 
	printf("                    :ysss+:mNMMMMNNmmds.        \n"); 
	printf("                `-//sssoo/:NMNMMMNMNNdy-        \n"); 
	printf("    -`/`       `omhyyhyyyshNMMNNNMMMNmy:        \n"); 
	printf("    :/::-`     `sdmdmmNMNMMMMMMNMNNNNms-        \n"); 
	printf("     /+++/-.....shdmmNMMNMMMMMMMMMNNNd+         \n");
	printf("     ./+oshyhhhddmhdmNMMMMMMMMMMMMNNds.         \n"); 
	printf("       `:/:.`````.:+ymmNMMNMMMNMMNNd/           \n"); 
	printf("                     -+shmNNMMMNmhy/            \n"); 
	printf("                          `..-ods:.             \n");
	printf("                               o:.`             \n");
	printf("                               :-.              \n");
	printf("                              `/-...            \n"); 
	printf("    Introducing the emu!     --``-/:`           \n"); 
	printf("                           .:/+:-.-::.          \n"); 
	printf("                          `.-///:-.`            \n");
	printf(" Website: http://www.ArcEmu.org	     			\n");
	printf(" Forums: http://www.ArcEmu.org/forums/          \n");
	printf(" Credits: http://www.ArcEmu.org/credits         \n");
	printf(" SVN: http://arcemu.info/svn/                   \n");
	printf(" Have fun!                                      \n");
	Log.Line();
#ifdef REPACK
	sLog.outString("Repack: %s | Author: %s | %s\n", REPACK, REPACK_AUTHOR, REPACK_WEBSITE);
#endif
	sLog.outString("==============================================================================");
	sLog.outString("");
	if(do_version)
		return;

	if(do_check_conf)
	{
		printf("Checking config file: %s\n", config_file);
		if(Config.MainConfig.SetSource(config_file, true))
			printf("  Passed without errors.\n");
		else
			printf("  Encountered one or more errors.\n");
		/* Remved useless die directive */
		/*
		string die;
		if(Config.MainConfig.GetString("die", "msg", &die) || Config.MainConfig.GetString("die2", "msg", &die))
			printf("Die directive received: %s", die.c_str());
		*/
		return;
	}
	
	sLog.outString("The key combination <Ctrl-C> will safely shut down the server at any time.");
	sLog.outString("");
	Log.Notice("System","Initializing Random Number Generators...");

	Log.Notice("Config", "Loading Config Files...");
	if(!Rehash())
		return;

	Log.Notice("ThreadMgr", "Starting...");
	ThreadPool.Startup();
   
	if(!startdb())
		return;

	Log.Notice("AccountMgr", "Starting...");



	// Spawn periodic function caller thread for account reload every 10mins
	int atime = Config.MainConfig.GetIntDefault("Rates", "AccountRefresh",600);
	atime *= 1000;
	//SpawnPeriodicCallThread(AccountMgr, AccountMgr::getSingletonPtr(), &AccountMgr::ReloadAccountsCallback, time);

	// Load conf settings..
	uint32 cport = Config.MainConfig.GetIntDefault("Listen", "ServerPort", 4322);
	string host = Config.MainConfig.GetStringDefault("Listen", "Host", "0.0.0.0");

	
	min_build = LOGON_MINBUILD;
	max_build = LOGON_MAXBUILD;

	string logon_pass = Config.MainConfig.GetStringDefault("LogonServer", "RemotePassword", "r3m0t3b4d");

	
	ThreadPool.ExecuteTask(new LogonConsoleThread);

	new SocketMgr;
	new SocketGarbageCollector;
	sSocketMgr.SpawnWorkerThreads();

	ListenSocket<AuthSocket> * cl = new ListenSocket<AuthSocket>(host.c_str(), cport);

	// Spawn auth listener
	// Spawn interserver listener
	bool authsockcreated = cl->IsOpen();
#ifdef WIN32
	if(authsockcreated)
		ThreadPool.ExecuteTask(cl);
#endif
	// hook signals
	sLog.outString("Hooking signals...");
	signal(SIGINT, _OnSignal);
	signal(SIGTERM, _OnSignal);
	signal(SIGABRT, _OnSignal);
#ifdef _WIN32
	signal(SIGBREAK, _OnSignal);
#else
	signal(SIGHUP, _OnSignal);
#endif

		/* write pid file */
	FILE * fPid = fopen("logonserver.pid", "w");
	if(fPid)
	{
		uint32 pid;
#ifdef WIN32
		pid = GetCurrentProcessId();
#else
		pid = getpid();
#endif
		fprintf(fPid, "%u", (unsigned int)pid);
		fclose(fPid);
	}
	uint32 loop_counter = 0;
	//ThreadPool.Gobble();
	sLog.outString("Success! Ready for connections");
	while(mrunning && authsockcreated )
	{
		if(!(++loop_counter % 20))	 // 20 seconds
			CheckForDeadSockets();

		if(!(loop_counter%300))	// 5mins
			ThreadPool.IntegrityCheck();

		if(!(loop_counter%5))
		{
			sSocketGarbageCollector.Update();
			CheckForDeadSockets();			  // Flood Protection
			UNIXTIME = time(NULL);
			g_localTime = *localtime(&UNIXTIME);
		}
		Sleep(1000);
	}

	sLog.outString("Shutting down...");
        signal(SIGINT, 0);
        signal(SIGTERM, 0);
        signal(SIGABRT, 0);
#ifdef _WIN32
        signal(SIGBREAK, 0);
#else
        signal(SIGHUP, 0);
#endif


	cl->Close();
	sSocketMgr.CloseAll();
#ifdef WIN32
	sSocketMgr.ShutdownThreads();
#endif
	sLogonConsole.Kill();
	delete LogonConsole::getSingletonPtr();

	// kill db
	sLog.outString("Waiting for database to close..");
	sLogonSQL->EndThreads();
	sLogonSQL->Shutdown();
	delete sLogonSQL;

	ThreadPool.Shutdown();

	// delete pid file
	remove("logonserver.pid");

	delete SocketMgr::getSingletonPtr();
	delete SocketGarbageCollector::getSingletonPtr();
	delete cl;
	printf("Shutdown complete.\n");
}

void OnCrash(bool Terminate)
{

}

void LogonServer::CheckForDeadSockets()
{
	_authSocketLock.Acquire();
	time_t t = time(NULL);
	time_t diff;
	set<AuthSocket*>::iterator itr = _authSockets.begin();
	set<AuthSocket*>::iterator it2;
	AuthSocket * s;

	for(itr = _authSockets.begin(); itr != _authSockets.end();)
	{
		it2 = itr;
		s = (*it2);
		++itr;

		diff = t - s->GetLastRecv();
		if(diff > 300)		   // More than 5mins
		{
			_authSockets.erase(it2);
			s->removedFromSet = true;
			s->Disconnect();
		}
	}
	_authSocketLock.Release();
}
